#include "CPPU.h"
#include <stdio.h>

////////////////////////////////////////////////////////////////////////////////////////////////////

CPPU::CPPU() {
    mTicks = 0;
    mRead  = NULL;
    mWrite = NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

CPPU::~CPPU() {}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CPPU::Update( int cycles ) {
    mTicks += cycles;

    UpdateLCDStatus();

    if( !LCDEnabled() ) {
        mTicks = 0;
        return;
    }

    if( mRead(0xff44) >= PPU_MAX_SCANLINE ) mWrite( 0xff44, 0 );

    if( mTicks >= PPU_SCANLINE_CYCLES ) {
        mTicks -= PPU_SCANLINE_CYCLES;
        // TODO: render scanline
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////

uint8_t CPPU::GetLCDStatus()const {
    return mRead( 0xff41 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

uint8_t CPPU::GetLCDMode()const {
    return ( (uint8_t)(GetLCDStatus() & 0x03) );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPPU::LCDEnabled()const {
    return ( (uint8_t)(mRead(0xff40) & 0x80) == 0x80 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPPU::Mode0InterruptEnabled()const {
    return ( (uint8_t)(GetLCDStatus() & 0x08) == 0x08 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPPU::Mode1InterruptEnabled()const {
    return ( (uint8_t)(GetLCDStatus() & 0x10) == 0x10 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPPU::Mode2InterruptEnabled()const {
    return ( (uint8_t)(GetLCDStatus() & 0x20) == 0x20 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CPPU::CoincidenceFlagInterruptEnabled()const {
    return ( (uint8_t)(GetLCDStatus() & 0x40) == 0x40 );
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CPPU::UpdateLCDStatus() {
    uint8_t status = GetLCDStatus();

    //printf( "    MODE #%i  TICKS: %3i", GetLCDMode(), mTicks );

    // el LCD permanece en modo #1 mientras esta deshabilitado
    if( !LCDEnabled() ) {
        status &= 0xfc;
        status |= 0x01;
        mWrite( 0xff41, status );
        mWrite( 0xff44, 0 );
        return;
    }

    // el LCD se encuentra operativo
    uint8_t mPendingInterrupts = 0;
    uint8_t current_mode       = GetLCDMode();

    // modo #2 (OAM)
    if( mTicks <= PPU_MODE_2_CYCLES ) {
        if( current_mode != 0x02 ) {
            status &= 0xfc;
            status |= 0x02;
            if( Mode2InterruptEnabled() ) mPendingInterrupts |= INTERRUPT_LCD;
        }
    }

    // modo #3 (OAM-VRAM)
    else if( mTicks <= PPU_MODE_3_CYCLES ) {
        if( current_mode != 0x03 ) status |= 0x03;
    }

    // modo #0 (H-Blank)
    else if( mTicks <= PPU_MODE_0_CYCLES ) {
        if( current_mode != 0 ) {
            status &= 0xfc;
            if( Mode0InterruptEnabled() ) mPendingInterrupts |= INTERRUPT_LCD;
        }
    }

    // modo #1 (V-Blank)
    else {
        status &= 0xfc;
        status |= 0x01;
        if( Mode1InterruptEnabled() ) mPendingInterrupts |= INTERRUPT_LCD;
    }

    // coincidence flag
    if( mRead(0xff44) == mRead(0xff45) ) {
        status |= 0x04;
        if( CoincidenceFlagInterruptEnabled() ) mPendingInterrupts |= INTERRUPT_LCD;
    }

    mWrite( 0xff41, status ); // actualizo el estado del LCD
    mWrite( 0xff0f, (uint8_t)(mRead(0xff0f) | mPendingInterrupts) ); // actualizo el flag de interrupciones
}

////////////////////////////////////////////////////////////////////////////////////////////////////